/*
 * Copyright 2002-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dynaspring.support;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.core.GenericCollectionTypeResolver;

/**
 * Adaptation of Spring's MapFactoryBean to allow multiple source maps.
 * 
 * @author Alessio Stalla (original Spring code by Juergen Hoeller)
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class MapFactoryBean extends AbstractFactoryBean<Map> {

	private List<Map> sourceMaps;

	private Class<? extends Map> targetMapClass = HashMap.class;

	/**
	 * Set the source Maps
	 */
	public void setSourceMaps(List<Map> sourceMaps) {
		this.sourceMaps = sourceMaps;
	}

	/**
	 * Set the class to use for the target Map. Can be populated with a fully
	 * qualified class name when defined in a Spring application context.
	 * <p>
	 * Default is a <code>java.util.HashMap</code>.
	 * 
	 * @see java.util.HashMap
	 */
	public void setTargetMapClass(Class targetMapClass) {
		if (targetMapClass == null) {
			throw new IllegalArgumentException("'targetMapClass' must not be null");
		}
		if (!Map.class.isAssignableFrom(targetMapClass)) {
			throw new IllegalArgumentException("'targetMapClass' must implement [java.util.Map]");
		}
		this.targetMapClass = targetMapClass;
	}

	public Class getObjectType() {
		return Map.class;
	}

	protected Map createInstance() {
		if (this.sourceMaps == null) {
			throw new IllegalArgumentException("'sourceMaps' is required");
		}
		Map result = BeanUtils.instantiateClass(this.targetMapClass);
		Class keyType = GenericCollectionTypeResolver.getMapKeyType(this.targetMapClass);
		Class valueType = GenericCollectionTypeResolver.getMapValueType(this.targetMapClass);
		keyType = keyType != null ? keyType : Object.class;
		valueType = valueType != null ? valueType : Object.class;
		TypeConverter converter = getBeanTypeConverter();
		for(Map sourceMap : sourceMaps) {
			if (keyType != Object.class || valueType != Object.class) {
				for(Iterator<Map.Entry> it = sourceMap.entrySet().iterator(); it.hasNext();) {
					Map.Entry e = it.next();
					result.put(converter.convertIfNecessary(e.getKey(), keyType),
							   converter.convertIfNecessary(e.getValue(), valueType));
				}
			} else {
				result.putAll(sourceMap);
			}
		}
		return result;
	}

}